package array

func Map[T, U any](slice []T, f func(T) U) []U {
	result := make([]U, len(slice))
	for i, v := range slice {
		result[i] = f(v)
	}
	return result
}

func Each[T any](slice []T, f func(T)) {
	for _, v := range slice {
		f(v)
	}
}

func Filter[T any](slice []T, f func(T) bool) []T {
	var result []T
	for _, v := range slice {
		if f(v) {
			result = append(result, v)
		}
	}
	return result
}

func Reduce[T, U any](slice []T, initial U, f func(U, T) U) U {
	result := initial
	for _, v := range slice {
		result = f(result, v)
	}
	return result
}

func Keys[K comparable, V any](m map[K]V) []K {
	keys := make([]K, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

func Values[K comparable, V any](m map[K]V) []V {
	values := make([]V, 0, len(m))
	for _, v := range m {
		values = append(values, v)
	}
	return values
}

func Merge[T any](slices ...[]T) []T {
	var result []T
	for _, slice := range slices {
		result = append(result, slice...)
	}
	return result
}

func Unique[T comparable](slice []T) []T {
	uniqueMap := make(map[T]struct{})
	result := make([]T, 0, len(slice))
	for _, v := range slice {
		if _, ok := uniqueMap[v]; !ok {
			uniqueMap[v] = struct{}{}
			result = append(result, v)
		}
	}
	return result
}

func Search[T comparable](slice []T, value T) int {
	for i, v := range slice {
		if v == value {
			return i
		}
	}
	return -1
}

func Shift[T any](slice *[]T) (T, bool) {
	if len(*slice) == 0 {
		var zeroValue T
		return zeroValue, false
	}

	firstElement := (*slice)[0]
	*slice = (*slice)[1:]

	return firstElement, true
}

func Unshift[T any](slice *[]T, elements ...T) {
	*slice = append(elements, *slice...)
}

func Push[T any](slice *[]T, elements ...T) {
	*slice = append(*slice, elements...)
}

func Pop[T any](slice *[]T) (T, bool) {
	if len(*slice) == 0 {
		var zeroValue T
		return zeroValue, false
	}
	lastIndex := len(*slice) - 1
	lastElement := (*slice)[lastIndex]
	*slice = (*slice)[:lastIndex]

	return lastElement, true
}

func Chunk[T any](slice []T, size int) [][]T {
	var result [][]T
	for i := 0; i < len(slice); i += size {
		end := i + size
		if end > len(slice) {
			end = len(slice)
		}
		result = append(result, slice[i:end])
	}
	return result
}

func Slice[T any](slice []T, start, end int) []T {
	if start < 0 {
		start = len(slice) + start
	}
	if end < 0 {
		end = len(slice) + end
	}
	if start > len(slice) {
		start = len(slice)
	}
	if end > len(slice) {
		end = len(slice)
	}
	return slice[start:end]
}

func Intersect[T comparable](slices ...[]T) []T {
	if len(slices) == 0 {
		return []T{}
	}
	result := make([]T, 0)
	firstSlice := slices[0]
	for _, v := range firstSlice {
		existsInAll := true
		for _, otherSlice := range slices[1:] {
			found := false
			for _, other := range otherSlice {
				if v == other {
					found = true
					break
				}
			}
			if !found {
				existsInAll = false
				break
			}
		}
		if existsInAll {
			result = append(result, v)
		}
	}
	return result
}

func Diff[T comparable](slices ...[]T) []T {
	if len(slices) == 0 {
		return []T{}
	}
	result := make([]T, 0)
	firstSlice := slices[0]
	for _, v := range firstSlice {
		notInOthers := true
		for _, otherSlice := range slices[1:] {
			found := false
			for _, other := range otherSlice {
				if v == other {
					found = true
					break
				}
			}
			if found {
				notInOthers = false
				break
			}
		}
		if notInOthers {
			result = append(result, v)
		}
	}
	return result
}

func Fill[T any](length int, value T) []T {
	result := make([]T, length)
	for i := range result {
		result[i] = value
	}
	return result
}

func Column[T any, U any](slice []T, column func(T) U) []U {
	result := make([]U, len(slice))
	for i, v := range slice {
		result[i] = column(v)
	}
	return result
}

func Reverse[T any](slice []T) []T {
	reversed := make([]T, len(slice))
	for i, v := range slice {
		reversed[len(slice)-1-i] = v
	}
	return reversed
}

func Where[T any](slice []T, condition func(T) bool) []T {
	var result []T
	for _, v := range slice {
		if condition(v) {
			result = append(result, v)
		}
	}
	return result
}

func GroupBy[T any, U comparable](slice []T, field func(T) U) map[U][]T {
	result := make(map[U][]T)
	for _, v := range slice {
		key := field(v)
		result[key] = append(result[key], v)
	}
	return result
}

func First[T any](slice []T) T {
	if len(slice) > 0 {
		return slice[0]
	}
	var zeroValue T
	return zeroValue
}

func Last[T any](slice []T) T {
	if len(slice) > 0 {
		return slice[len(slice)-1]
	}
	var zeroValue T
	return zeroValue
}

func Reject[T any](slice []T, condition func(T) bool) []T {
	result := make([]T, 0)
	for _, v := range slice {
		if !condition(v) {
			result = append(result, v)
		}
	}
	return result
}

func Combine[K comparable, V any](keys []K, values []V) map[K]V {
	if len(keys) != len(values) {
		return nil
	}
	result := make(map[K]V)
	for i, key := range keys {
		result[key] = values[i]
	}
	return result
}

func Forget[K comparable, V any](m map[K]V, keysToRemove ...K) {
	for _, key := range keysToRemove {
		delete(m, key)
	}
}

func Pull[K comparable, V any](m map[K]V, key K) (V, bool) {
	value, exists := m[key]
	if exists {
		delete(m, key)
	}
	return value, exists
}

func Only[K comparable, V any](m map[K]V, keys []K) []V {
	result := make([]V, 0)
	for _, key := range keys {
		if value, exists := m[key]; exists {
			result = append(result, value)
		}
	}

	return result
}
